use std::{
	collections::HashMap,
	path::{Path, PathBuf},
};

use regex::Regex;
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};

use crate::Access;

#[derive(Default, Serialize, Deserialize)]
pub struct Config {
	pub in_dir: String,
	pub needs: HashMap<String, ClassConfig>,
	#[serde(default)]
	pub out_dir: String,
	#[serde(skip)]
	pub loads: HashMap<String, Access>,
	#[serde(skip)]
	pub achive: Option<Achive>,
}
impl Config {
	pub fn init(&mut self, cfg_file: &String) {
		let mut needs: HashMap<String, ClassConfig> = Default::default();
		std::mem::swap(&mut self.needs, &mut needs);
		for (k, v) in needs {
			self.needs.insert(k.replace(".", "/"), v);
		}
		if self.out_dir.is_empty() {
			let out_dir: PathBuf = cfg_file.into();
			self.out_dir = format!("{}", out_dir.with_extension("").display());
		}
		self.load_achive();
	}
	pub fn name<'a>(&'a self, path: &'a str) -> Name<'a> {
		Name(self, path)
	}
	pub fn load_achive(&mut self) {
		let in_dir = &self.in_dir;
		let in_dir: Option<PathBuf> = (|| {
			if self.in_dir.starts_with("compileSdk") {
				let version = in_dir.split(":").last()?;
				let version: u32 = version.parse().ok()?;
				let android_home: PathBuf = std::env::var("ANDROID_HOME").ok()?.into();
				Some(android_home.join(format!("platforms/android-{version}")))
			} else {
				Some(in_dir.into())
			}
		})();
		if let Some(in_dir) = in_dir {
			self.achive = Achive::new(&in_dir.join("android.jar"));
		}
	}
	pub fn load_class(&self, path: &String) -> Option<Vec<u8>> {
		self.achive.as_ref()?.load(path)
	}
	pub fn check_field(&self, class: &String, name: &str) -> bool {
		if let Some(c) = self.needs.get(class) {
			c.check_field(name)
		} else {
			false
		}
	}
	pub fn check_method(&self, class: &String, name: &str) -> bool {
		if let Some(c) = self.needs.get(class) {
			c.check_method(name)
		} else {
			false
		}
	}
}
pub struct Name<'a>(pub &'a Config, pub &'a str);
impl<'a> Name<'a> {
	pub fn java_to_rust(name: &str) -> String {
		name.split("/").last().unwrap().replace("$", "")
	}
	pub fn is_lang(&self) -> bool {
		match self.1 {
			"java/lang/Object" => true,
			"java/lang/String" => true,
			_ => false,
		}
	}
	pub fn as_java(&self) -> &str {
		match self.1 {
			"java/lang/Object" => "",
			"java/lang/String" => "",
			_ => self.1,
		}
	}
	pub fn to_rust(&self) -> String {
		match self.1 {
			"java/lang/Object" => "JObject".into(),
			"java/lang/String" => "JString".into(),
			_ => Self::java_to_rust(&self.1),
		}
	}
	pub fn to_rust_real(&self) -> String {
		match self.1 {
			"java/lang/Object" => "JObject".into(),
			"java/lang/String" => "JString".into(),
			_ => {
				if let Some(access) = self.0.loads.get(self.1) {
					let x = self.1.replace("/", "::").replace("$", "");
					if access.is_interface() {
						format!("crate::{x}Object")
					} else {
						format!("crate::{x}")
					}
				} else {
					"JObject".into()
				}
			}
		}
	}
	pub fn to_snake(&self) -> String {
		casbab::snake(&self.to_rust())
	}
	pub fn to_upper_snake(&self) -> String {
		casbab::screaming_snake(&self.to_rust())
	}
	pub fn fmt_use(&'_ self) -> FmtNameUse<'_> {
		FmtNameUse(self)
	}
}

pub struct FmtNameUse<'a>(pub &'a Name<'a>);
impl<'a> std::fmt::Display for FmtNameUse<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		if !self.0.is_lang() {
			f.write_fmt(format_args!("use crate::{};", self.0 .1.replace("/", "::").replace("$", ""),))?;
		}
		Ok(())
	}
}

#[derive(Default, Serialize, Deserialize)]
pub struct ClassConfig {
	#[serde(skip_serializing_if = "ignore_empty")]
	#[serde(default)]
	pub field_allow: Vec<SerdeRegex>,
	#[serde(skip_serializing_if = "ignore_empty")]
	#[serde(default)]
	pub field_block: Vec<SerdeRegex>,
	#[serde(skip_serializing_if = "ignore_empty")]
	#[serde(default)]
	pub method_allow: Vec<SerdeRegex>,
	#[serde(skip_serializing_if = "ignore_empty")]
	#[serde(default)]
	pub method_block: Vec<SerdeRegex>,
}
impl ClassConfig {
	pub fn check_field(&self, name: &str) -> bool {
		// if !self.field_allow.is_empty() {
		let mut allow = false;
		for regex in self.field_allow.iter() {
			if regex.is_match(name) {
				allow = true;
			}
		}
		if !allow {
			return false;
		}
		// }
		for regex in self.field_block.iter() {
			if regex.is_match(name) {
				return false;
			}
		}
		true
	}
	pub fn check_method(&self, name: &str) -> bool {
		if !self.method_allow.is_empty() {
			let mut allow = false;
			for regex in self.method_allow.iter() {
				if regex.is_match(name) {
					allow = true;
				}
			}
			if !allow {
				return false;
			}
		}
		for regex in self.method_block.iter() {
			if regex.is_match(name) {
				return false;
			}
		}
		true
	}
}

pub struct SerdeRegex(pub Regex);
impl SerdeRegex {
	pub fn new(v: &str) -> Option<SerdeRegex> {
		Regex::new(v).map(|v| SerdeRegex(v)).ok()
	}
}
impl std::ops::Deref for SerdeRegex {
	type Target = Regex;
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}
impl Serialize for SerdeRegex {
	fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer,
	{
		s.serialize_str(self.0.as_str())
	}
}
impl<'de> Deserialize<'de> for SerdeRegex {
	fn deserialize<D>(d: D) -> Result<Self, D::Error>
	where
		D: Deserializer<'de>,
	{
		struct RegexVisitor;
		impl<'d> Visitor<'d> for RegexVisitor {
			type Value = SerdeRegex;

			fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
				formatter.write_str("a borrowed string")
			}
			fn visit_borrowed_str<E>(self, v: &'d str) -> Result<Self::Value, E>
			where
				E: serde::de::Error,
			{
				if let Ok(v) = Regex::new(v) {
					Ok(SerdeRegex(v))
				} else {
					Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self))
				}
			}
		}
		d.deserialize_str(RegexVisitor)
	}
}

fn ignore_empty(data: &Vec<SerdeRegex>) -> bool {
	data.is_empty()
}

pub struct Achive(
	pub makepad_miniz::zip_file::ZipCentralDirectory,
	pub Vec<u8>,
	pub std::collections::HashMap<String, usize>,
);
impl Achive {
	pub fn new(path: &Path) -> Option<Self> {
		let bytes = std::fs::read(path).ok()?;
		let mut cursor = std::io::Cursor::new(&bytes);
		let zip = makepad_miniz::zip_file::zip_read_central_directory(&mut cursor).ok()?;
		let mut files = HashMap::new();
		for (i, f) in zip.file_headers.iter().enumerate() {
			files.insert(f.file_name.clone(), i);
		}
		Some(Self(zip, bytes, files))
	}
	pub fn load(&self, path: &String) -> Option<Vec<u8>> {
		let idx = self.2.get(path)?;
		let file = self.0.file_headers.get(*idx)?;
		let mut cursor = std::io::Cursor::new(&self.1);
		let bytes = file.extract(&mut cursor).ok()?;
		Some(bytes)
	}
}
